AArch64 (ARM64) のスタック操作
#ARM #アセンブリ言語
AArch64(ARM64)でスタックを操作しようとするもエラーが出まくるので、以下を参考にAArch64のスタック操作の基本を練習した。
Using the Stack in AArch32 and AArch64
Using the Stack in AArch64: Implementing Push and Pop
サンプルコード
以下はHello Worldを出力するコード。これをベースに色々と手を入れる。実行するにはXcodeに雑にソースを追加しCommand-Rキーを押す。
code:hello.S
.global _main
.align 2
_main:
// print Hello World!
mov x0, #1 // 1 = StdOut
adr x1, helloworld // string to print
mov X2, #13 // length of our string
mov X16, #4 // Unix write system call
svc #0x80 // Call kernel to output the string
// exit
mov x0, #0 // Use 0 return code
mov x16, #1 // System call number 1 terminates this program
svc #0x80 // Call kernel to terminate the program
helloworld: .ascii "Hello World!\n"
実行結果はこんな感じ
https://gyazo.com/497b23be9a6aa44b395e849f64707592
ブレークポイントで止める
アセンブリソースにもブレークポイントを設定できる
https://gyazo.com/22e2734c374d5262e5d66e15a09a1ab0
レジスタに値をセット
code:diff
.global _main
.align 2
_main:
+ // レジスタに値をセット
+ mov x0, 0x00
+ mov x1, 0x10
+ mov x2, 0x30
+ mov x3, 0x40
+
https://gyazo.com/81556ff7f83aa7590d954f08db80b97f
スタックへのPUSHとPOP
code:diff
.global _main
.align 2
_main:
+ // レジスタに値をセット
+ mov x0, 0x00
+ mov x1, 0x10
+ mov x2, 0x20
+ mov x3, 0x30
+
+ str x0, sp, #-16! // push {x0}
+ str x1, sp, #-16! // push {x1}
+ str x2, sp, #-16! // push {x2}
+ str x3, sp, #-16! // push {x3}
+
+ ldr x0, sp, #16 // pop {x3}
+ ldr x1, sp, #16 // pop {x2}
+ ldr x2, sp, #16 // pop {x1}
+ ldr x3, sp, #16 // pop {x0}
+
レジスタへ設定した値をスタックへPUSH
code:diff
+ str x0, sp, #-16! // push {x0}
+ str x1, sp, #-16! // push {x1}
+ str x2, sp, #-16! // push {x2}
+ str x3, sp, #-16! // push {x3}
+
https://gyazo.com/a26402b3e11e1a8bc01f2a673f7c0f5d
スタックからPOPした値をレジスタへ復帰
code:diff
+ ldr x0, sp, #16 // pop {x3}
+ ldr x1, sp, #16 // pop {x2}
+ ldr x2, sp, #16 // pop {x1}
+ ldr x3, sp, #16 // pop {x0}
https://gyazo.com/651a2e9df4959ae666fb7fbc803fcf64
スタック操作とSPレジスタの値
スタックへのPUSH(str x0, [sp, #-16]!)を行うと SP レジスタの値が自動で減少し、スタックからのPOPを行うと SP レジスタの値が自動で増加するみたい
code:asm
// スタック操作前: sp = 0x000000016fdff430
str x0, sp, #-16! // sp = 0x000000016fdff420 (=> 16減少)
str x1, sp, #-16! // sp = 0x000000016fdff410 (=> 16減少)
str x2, sp, #-16! // sp = 0x000000016fdff400 (=> 16減少)
str x3, sp, #-16! // sp = 0x000000016fdff3f0 (=> 16減少)
ldr x0, sp, #16 // sp = 0x000000016fdff400 (=> 16増加)
ldr x1, sp, #16 // sp = 0x000000016fdff410 (=> 16増加)
ldr x2, sp, #16 // sp = 0x000000016fdff420 (=> 16増加)
ldr x3, sp, #16 // sp = 0x000000016fdff430 (=> 16増加)
スタックの実験で利用したコード
code:hello.s
.global _main
.align 2
_main:
// レジスタに値をセット
mov x0, 0x00
mov x1, 0x10
mov x2, 0x20
mov x3, 0x30
mov x4, 0x40
mov x5, 0x50
mov x6, 0x60
mov x7, 0x70
// スタックへのPUSH
str x0, sp, #-16! // push {x0}
str x1, sp, #-16! // push {x1}
str x2, sp, #-16! // push {x2}
str x3, sp, #-16! // push {x3}
// スタックからのPOP
ldr x0, sp, #16 // pop {x3}
ldr x1, sp, #16 // pop {x2}
ldr x2, sp, #16 // pop {x1}
ldr x3, sp, #16 // pop {x0}
// スタックへのPUSH (Part2)
sub sp, sp, #32
stp x4, x5, sp
stp x6, x7, sp, #16
// スタックからのPOP (Part2)
ldp x7, x6, sp // pop {x7, x6}
ldp x5, x4, sp, #16 // pop {x7, x6}
add sp, sp, #32
// print Hello World!
mov x0, #1 // 1 = StdOut
adr x1, helloworld // string to print
mov X2, #13 // length of our string
mov X16, #4 // Unix write system call
svc #0x80 // Call kernel to output the string
// exit
mov x0, #0 // Use 0 return code
mov x16, #1 // System call number 1 terminates this program
svc #0x80 // Call kernel to terminate the program
helloworld: .ascii "Hello World!\n"
SPをベースレジスタとして利用した場合の16バイトアライメント制限
SPをベースレジスタとしてメモリアクセスする場合、16バイトのアライメントに従う必要がある。
code:asm
// スタックへのPUSH
str x0, sp, #-8! // push {x0} // ベースのSPから16バイトを読み込むため、ここではアライメントエラーにならない
str x1, sp, #-8! // push {x1} // 8バイト名から16バイトを読もうとするため、アライメントエラーが発生する
str x2, sp, #-8! // push {x2}
str x3, sp, #-8! // push {x3}
SP以外のレジスタをベースレジスタとして利用する場合は、8バイトのアライメントでも大丈夫
code:asm
// x8 をベースレジスタとして利用
mov x8, sp
// スタックへのPUSH
str x0, x8, #-8! // push {x0}
str x1, x8, #-8! // push {x1}
str x2, x8, #-8! // push {x2}
str x3, x8, #-8! // push {x3}
// スタックからのPOP
ldr x0, x8, #8 // pop {x3}
ldr x1, x8, #8 // pop {x2}
ldr x2, x8, #8 // pop {x1}
ldr x3, x8, #8 // pop {x0}
リンクレジスタ(lr)とフレームポインタ(fp)の退避のしかた
stpとldpを使ってふたつのレジスタを同時にpush, popできる。lrとfpは常にペアで退避させるので、stpとldpを使うと良さそう
code:asm
// (おまけ)fpとlrのPUSHとPOP
stp fp, lr, sp, -16!; // push {fp, lr}
ldp fp, lr, sp, 16 // pop {fp, lr}
スタックへのPUSHとPOP(別解)
以下のようなスタックの操作の仕方もできる(こっちの書き方の方がオーソドックスな気がする)
code:asm
// スタックへPUSH
sub sp, sp, #32 // スタック領域を確保
stp x4, x5, sp // PUSH {x4, x5}
stp x6, x7, sp, #16 // PUSH {x6, x7}
// スタックからPOP
ldp x7, x6, sp // POP {x7, x6}
ldp x5, x4, sp, #16 // POP {x7, x6}
add sp, sp, #32 // スタック領域を解放